#include <stdio.h>
#include <libavutil/log.h>
#include <libavutil/fifo.h>
#include <stdlib.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>

#define AUDIO_BUFFER_SIZE 1024;
typedef struct _MyPacketEle {
  AVPacket *pkt;
} MyPacketEle;

typedef struct _PacketQueue {
  AVFifo *pkts;
  int nb_packets;
  int size;
  int64_t duration;

  SDL_mutex *mutex;
  SDL_cond *cond;

} PacketQueue;

typedef struct _VideoState {
  AVCodecContext *aCtx;
  AVPacket *aPkt;
  AVFrame *aFrame;

  struct SwrContext *swr_ctx;

  uint8_t *audio_buf;
  uint audio_buf_size;
  int audio_buf_index;

  AVCodecContext *vCtx;
  AVPacket *vPkt;
  AVFrame *vFrame;

  SDL_Texture *texture;
  PacketQueue audioQueue;
} VideoState;

static int w_width = 640;
static int w_height = 480;
static SDL_Window *win = NULL;
static SDL_Renderer *renderer = NULL;
static Uint32 pix_format = 0;

static int packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->pkts = av_fifo_alloc2(1, sizeof(MyPacketEle), AV_FIFO_FLAG_AUTO_GROW);
  if (!q->pkts) {
    return AVERROR(ENOMEM);
  }

  q->mutex = SDL_CreateMutex();
  if (!q->mutex) {
    return AVERROR(ENOMEM);
  }
  q->cond = SDL_CreateCond();
  if (!q->cond) {
    return AVERROR(ENOMEM);
  }
  return 0;
}
static int packet_queue_put_priv(PacketQueue *q, AVPacket *pkt) {
  MyPacketEle mypkt;
  int ret;

  mypkt.pkt = pkt;
  ret = av_fifo_write(q->pkts, &mypkt, 1);
  if (ret < 0) {
    return ret;
  }
  q->nb_packets++;
  q->size += mypkt.pkt->size + sizeof(mypkt);
  q->duration += mypkt.pkt->duration;

  SDL_CondSignal(q->cond);
  return 0;
}

static int packet_queue_put(PacketQueue *q, AVPacket *pkt) {
  AVPacket *pkt1;
  int ret;

  pkt1 = av_packet_alloc();
  if (!pkt1) {
    av_packet_unref(pkt);
    return -1;
  }
  av_packet_move_ref(pkt1, pkt);
  SDL_LockMutex(q->mutex);
  ret = packet_queue_put_priv(q, pkt1);
  SDL_UnlockMutex(q->mutex);
  if (ret < 0) {
    av_packet_free(&pkt1);
  }
  return ret;
}

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
  MyPacketEle mypkt;
  int ret;
  SDL_LockMutex(q->mutex);
  for (;;) {
    if (av_fifo_read(q->pkts, &mypkt, 1) >= 0) {
      q->nb_packets--;
      q->size -= mypkt.pkt->size + sizeof(mypkt);
      q->duration -= mypkt.pkt->duration;
      av_packet_move_ref(pkt, mypkt.pkt);
      av_packet_free(&mypkt.pkt);
      ret = 1;
      break;
    } else if (!block) {
      ret = 0;
      break;
    } else {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}

static void packet_queue_flush(PacketQueue *q) {
  MyPacketEle mypkt;
  SDL_LockMutex(q->mutex);
  while (av_fifo_read(q->pkts, &mypkt, 1) > 0) {
    av_packet_free(&mypkt.pkt);
  }
  q->nb_packets = 0;
  q->size = 0;
  q->duration = 0;
  SDL_UnlockMutex(q->mutex);
}

static void packet_queue_destroy(PacketQueue *q) {
  packet_queue_flush(q);
  av_fifo_freep2(&q->pkts);
  SDL_DestroyMutex(q->mutex);
  SDL_DestroyCond(q->cond);
}
static int audio_decode_frame(VideoState *vs) {
  int ret = -1;
  int len2;
  int data_size = 0;
  AVPacket pkt;
  for (;;) {
    if (packet_queue_get(&vs->audioQueue, &pkt, 1) < 0) {
      return -1;
    }

    ret = avcodec_send_packet(vs->aCtx, &pkt);
    if (ret < 0) {
      av_log(vs->aCtx, AV_LOG_ERROR, "failed to send packet to audio decoder: %s \n", av_err2str(ret));
      goto __OUT;
    }

    while (ret >= 0) {
      ret = avcodec_receive_frame(vs->aCtx, vs->aFrame);
      if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
        ret = 0;
        goto __OUT;
      } else if (ret < 0) {
        ret = -1;
        av_log(vs->aCtx, AV_LOG_ERROR, "failed to receive frame to audio decoder \n");
        goto __OUT;
      }

      if (!vs->swr_ctx) {
        AVChannelLayout in_ch_layout, out_ch_layout;
        av_channel_layout_copy(&in_ch_layout, &vs->aCtx->ch_layout);
        av_channel_layout_copy(&out_ch_layout, &in_ch_layout);
        if (vs->aCtx->sample_fmt != AV_SAMPLE_FMT_S16) {
          swr_alloc_set_opts2(&vs->swr_ctx,
                              &out_ch_layout,
                              AV_SAMPLE_FMT_S16,
                              vs->aCtx->sample_rate,
                              &in_ch_layout,
                              vs->aCtx->sample_fmt,
                              vs->aCtx->sample_rate,
                              0,
                              NULL);
          swr_init(vs->swr_ctx);
        }
      }
      if (vs->swr_ctx) {
        const uint8_t **in = (const uint8_t **) vs->aFrame->extended_data;
        int in_count = vs->aFrame->nb_samples;
        uint8_t **out = &vs->audio_buf;
        int out_count = vs->aFrame->nb_samples + 512;
        int out_size =
            av_samples_get_buffer_size(NULL,
                                       vs->aFrame->ch_layout.nb_channels,
                                       out_count,
                                       AV_SAMPLE_FMT_S16,
                                       0);
        av_fast_malloc(&vs->audio_buf, &vs->audio_buf_size, out_size);

        len2 = swr_convert(vs->swr_ctx,
                           out,
                           out_count,
                           in,
                           in_count);
        data_size = len2 * vs->aFrame->ch_layout.nb_channels * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
      } else {
        vs->audio_buf = vs->aFrame->data[0];
        data_size = av_samples_get_buffer_size(NULL,
                                               vs->aFrame->ch_layout.nb_channels,
                                               vs->aFrame->nb_samples,
                                               vs->aFrame->format,
                                               1);
      }
      av_packet_unref(&pkt);
      av_frame_unref(vs->aFrame);
      return data_size;
    }
  }

  __OUT:
  return ret;
}

void sdl_audio_callback(void *userData, Uint8 *stream, int len) {
  int audio_size = 0;
  int len1 = 0;
  VideoState *vs = (VideoState *) userData;
  while (len > 0) {
    if (vs->audio_buf_index >= vs->audio_buf_size) {
      audio_size = audio_decode_frame(vs);
      if (audio_size < 0) {
        vs->audio_buf_size = AUDIO_BUFFER_SIZE;
        vs->audio_buf = NULL;
      } else {
        vs->audio_buf_size = audio_size;
      }
      vs->audio_buf_index = 0;
    }
    len1 = vs->audio_buf_size - vs->audio_buf_index;
    if (len1 > len) {
      len1 = len;
    }
    if (vs->audio_buf) {
      memcpy(stream, (uint8_t *) vs->audio_buf + vs->audio_buf_index, len1);
    } else {
      memset(stream, 0, len1);
    }
  }
  len -= len1;
  stream += len1;
  vs->audio_buf_index += len1;
}

static void render(VideoState *vs) {
  SDL_UpdateYUVTexture(vs->texture, NULL,
                       vs->vFrame->data[0],
                       vs->vFrame->linesize[0],
                       vs->vFrame->data[1],
                       vs->vFrame->linesize[1],
                       vs->vFrame->data[2],
                       vs->vFrame->linesize[2]);
  SDL_RenderClear(renderer);
  SDL_RenderCopy(renderer, vs->texture, NULL, NULL);
  SDL_RenderPresent(renderer);
}

static int decode(VideoState *vs) {
  int ret = -1;
  ret = avcodec_send_packet(vs->vCtx, vs->vPkt);
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "failed to send packet to decoder: %s \n", av_err2str(ret));
    goto __OUT;
  }

  while (ret >= 0) {
    ret = avcodec_receive_frame(vs->vCtx, vs->vFrame);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
      ret = 0;
      goto __OUT;
    } else if (ret < 0) {
      ret = -1;
      goto __OUT;
    }
    render(vs);
  }
  __OUT:
  return ret;
}

int main(int argc, char *argv[]) {
  char *src;
  int ret = -1;
  int vIndex = -1;
  int aIndex = -1;
  int video_width = 0;
  int video_height = 0;
  AVFormatContext *pFmtCtx = NULL;
  AVStream *aInStream = NULL;
  AVStream *vInStream = NULL;
  SDL_Texture *texture = NULL;
  const AVCodec *vDec = NULL;
  const AVCodec *aDec = NULL;
  AVCodecContext *vCtx = NULL;
  AVCodecContext *aCtx = NULL;
  AVPacket *pkt = NULL;
  AVPacket *vPkt = NULL;
  AVFrame *vFrame = NULL;
  AVPacket *aPkt = NULL;
  AVFrame *aFrame = NULL;
  VideoState *vs = NULL;
  SDL_Event event;

  //1. 判断输入参数
  av_log_set_level(AV_LOG_DEBUG);
  if (argc < 2) {
    av_log(NULL, AV_LOG_INFO, "arguments must be more than 2\n");
    exit(-1);
  }
  src = argv[1];
  vs = av_mallocz(sizeof(VideoState));
  if (!vs) {
    av_log(NULL, AV_LOG_ERROR, "no memory\n");
    goto __END;
  }
  //2. 初始化SDL，并创建窗口和Render
  //2.1 initialize SDL
  if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    return -1;
  }

  //2.2 creat window from SDL
  win = SDL_CreateWindow("Simple Player",
                         SDL_WINDOWPOS_UNDEFINED,
                         SDL_WINDOWPOS_UNDEFINED,
                         w_width, w_height,
                         SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE);
  if (!win) {
    fprintf(stderr, "Failed to create window, %s\n", SDL_GetError());
    goto __END;
  }

  //2.3
  renderer = SDL_CreateRenderer(win, -1, 0);

  //3. 打开多媒体文件，并获得流信息
  ret = avformat_open_input(&pFmtCtx, src, NULL, NULL);
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
    goto __END;
  }
  //3.2
  ret = avformat_find_stream_info(pFmtCtx, NULL);
  if (ret < 0) {
    av_log(NULL, AV_LOG_ERROR, "%s\n", av_err2str(ret));
    goto __END;
  }

  //4. 查找最好的视频流

  for (int i = 0; i < pFmtCtx->nb_streams; i++) {
    if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO
        && vIndex < 0) {
      vIndex = i;
    }
    if (pFmtCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO
        && aIndex < 0) {
      aIndex = i;
    }
    if (aIndex > -1 && vIndex > -1) {
      break;
    }
  }

  if (vIndex == -1) {
    av_log(NULL, AV_LOG_ERROR, "don't find video stream\n");
    goto __END;
  }
  if (aIndex == -1) {
    av_log(NULL, AV_LOG_ERROR, "don't find audio stream\n");
    goto __END;
  }

  //5. 根据流中的codec_id, 获得解码器
  aInStream = pFmtCtx->streams[aIndex];
  vInStream = pFmtCtx->streams[vIndex];

  vDec = avcodec_find_decoder(vInStream->codecpar->codec_id);
  if (!vDec) {
    av_log(NULL, AV_LOG_ERROR, "don't find Codec: libx264\n");
    goto __END;
  }
  //6. 创建解码器上下文
  vCtx = avcodec_alloc_context3(vDec);
  if (!vCtx) {
    av_log(NULL, AV_LOG_ERROR, "no memory!\n");
    goto __END;
  }

  //7. 从视频流中拷贝解码器参数到解码器上下文中
  ret = avcodec_parameters_to_context(vCtx, vInStream->codecpar);
  if (ret < 0) {
    av_log(vCtx, AV_LOG_ERROR, "could not copy codecpar to codec ctx!\n");
    goto __END;
  }
  //8. 绑定解码器上下文
  ret = avcodec_open2(vCtx, vDec, NULL);
  if (ret < 0) {
    av_log(vCtx, AV_LOG_ERROR, "don't open codec %s\n", av_err2str(ret));
    goto __END;
  }
  //9. 根据视频的宽/高创建纹理
  pix_format = SDL_PIXELFORMAT_IYUV;

  //create texture for render
  video_height = vCtx->height;
  video_width = vCtx->width;
  texture = SDL_CreateTexture(renderer,
                              pix_format,
                              SDL_TEXTUREACCESS_STREAMING,
                              video_width,
                              video_height);
  if (!texture) {
    SDL_Log("failed to create Texture");
    goto __END;
  }
  vs->texture = texture;

  aDec = avcodec_find_decoder(aInStream->codecpar->codec_id);
  if (!aDec) {
    av_log(NULL, AV_LOG_ERROR, "don't find Codec: acc\n");
    goto __END;
  }
  //6. 创建解码器上下文
  aCtx = avcodec_alloc_context3(aDec);
  if (!aCtx) {
    av_log(NULL, AV_LOG_ERROR, "no memory!\n");
    goto __END;
  }

  //7. 从视频流中拷贝解码器参数到解码器上下文中
  ret = avcodec_parameters_to_context(aCtx, aInStream->codecpar);
  if (ret < 0) {
    av_log(aCtx, AV_LOG_ERROR, "could not copy codecpar to codec ctx!\n");
    goto __END;
  }
  //8. 绑定解码器上下文
  ret = avcodec_open2(aCtx, aDec, NULL);
  if (ret < 0) {
    av_log(aCtx, AV_LOG_ERROR, "don't open codec %s\n", av_err2str(ret));
    goto __END;
  }
  packet_queue_init(&vs->audioQueue);

  //10. 从多媒体文件中读取数据，进行解码
  //10.1
  aFrame = av_frame_alloc();
  if (!aFrame) {
    av_log(NULL, AV_LOG_ERROR, "no memory\n");
    goto __END;
  }
  //10.2
  aPkt = av_packet_alloc();
  if (!aPkt) {
    av_log(NULL, AV_LOG_ERROR, "no memory\n");
    goto __END;
  }
  vFrame = av_frame_alloc();
  if (!vFrame) {
    av_log(NULL, AV_LOG_ERROR, "no memory\n");
    goto __END;
  }
  //10.2
  vPkt = av_packet_alloc();
  if (!vPkt) {
    av_log(NULL, AV_LOG_ERROR, "no memory\n");
    goto __END;
  }

  pkt = av_packet_alloc();
  if (!pkt) {
    av_log(NULL, AV_LOG_ERROR, "no memory\n");
    goto __END;
  }



  SDL_AudioSpec wanted_spec;
  wanted_spec.freq = aCtx->sample_rate;
  wanted_spec.format = AUDIO_S16SYS;
  wanted_spec.channels = aCtx->ch_layout.nb_channels;
  wanted_spec.silence = 0;
  wanted_spec.samples = AUDIO_BUFFER_SIZE;
  wanted_spec.callback = sdl_audio_callback;
  wanted_spec.userdata = (void *) vs;
  SDL_AudioSpec spec;

  if (SDL_OpenAudio(&wanted_spec, &spec) < 0) {
    av_log(NULL, AV_LOG_ERROR, "failed to open audio device! \n");
    goto __END;
  }
  SDL_PauseAudio(0);


  vs->aCtx = aCtx;
  vs->aPkt = aPkt;
  vs->aFrame = aFrame;

  vs->vCtx = vCtx;
  vs->vPkt = vPkt;
  vs->vFrame = vFrame;
  while (av_read_frame(pFmtCtx, pkt) >= 0) {
    if (pkt->stream_index == vIndex) {
      av_packet_move_ref(vs->vPkt, pkt);
      //11. 对解码后的视频帧进行渲染
      decode(vs);
    } else if (pkt->stream_index == aIndex) {
      packet_queue_put(&vs->audioQueue, pkt);
    } else {
      av_packet_unref(pkt);
    }
    //12. 处理SDL事件
    SDL_PollEvent(&event);
    switch (event.type) {
      case SDL_QUIT:goto __QUIT;
      default:break;
    }
  }
  vs->vPkt = NULL;
  decode(vs);
  __QUIT:
  ret = 0;
  __END:
  //12. 收尾，释放资源
  if (vFrame) {
    av_frame_free(&vFrame);
  }

  if (vPkt) {
    av_packet_free(&vPkt);
  }

  if (vCtx) {
    avcodec_free_context(&vCtx);
  }

  if (aFrame) {
    av_frame_free(&aFrame);
  }

  if (aPkt) {
    av_packet_free(&aPkt);
  }

  if (aCtx) {
    avcodec_free_context(&aCtx);
  }

  if (pkt) {
    av_packet_free(&pkt);
  }

  if (pFmtCtx) {
    avformat_close_input(&pFmtCtx);
  }

  if (renderer) {
    SDL_DestroyRenderer(renderer);
  }

  if (texture) {
    SDL_DestroyTexture(texture);
  }

  if(vs){
    av_free(vs);
  }

  if (win) {
    SDL_DestroyWindow(win);
  }
  SDL_Quit();
  printf("Hello, World!\n");
  return ret;
}
